<?php

/**
 * @copyright Michiel Hakvoort 2010
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD
 * @package mangrove
 * @subpackage core
 * @filesource
 */

/*
 * Copyright (c) 2010 Michiel Hakvoort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

namespace mg;

/**
 * A storage engine which delegates reads and write to an underlying dba.
 *
 */
class DBAStorage extends AbstractStorage {

    private $handle;

    private $key = false;

    private $file = null;

    private $canRead = false;
    private $canWrite = false;

    private static $lockFlag = null;
    private static $handler = null;

    public function __construct($storageName, $root, $os) {
        parent :: __construct($storageName);


        $this->file = $root . DS . $storageName;

        if(self :: $lockFlag === null) {
            if($os === Runtime :: OS_WIN) {
                self :: $lockFlag = 'l';
            } else {
                self :: $lockFlag = 'd';
            }
        }

        if(self :: $handler === null) {
            $preferredHandlers = array('db4'=>0, 'qdbm'=>1, 'gdbm'=>2, 'db3'=>3, 'db2'=>4, 'ndbm'=>5, 'dbm'=>6, 'flatfile'=>7);
            $handlers = dba_handlers(true);

            // Berkeley DB 4.8.26: (December 18, 2009)
            //			if(isset($handlers['db4']) && mb_strpos($handlers['db4'], '4.8.26') !== false) {
            //				unset($preferredHandlers['db4']);
            //			}

                $score = 9;
                foreach($handlers as $handler => $_) {
                    if(isset($preferredHandlers[$handler]) && $preferredHandlers[$handler] < $score) {
                        $score = $preferredHandlers[$handler];
                        self :: $handler = $handler;
                    }
                }
                	
            }
        }

        public function offsetSet($offset, $value) {
            $this->openRW();

            dba_replace($offset, $value, $this->handle);
            return $value;
        }

        public function offsetExists($offset) {
            $this->openR();

            if($this->handle) {
                return dba_exists($offset, $this->handle);
            } else {
                return false;
            }
        }

        public function offsetGet($offset) {
            $this->openR();

            if($this->handle) {
                return dba_fetch($offset, $this->handle);
            } else {
                return false;
            }
        }

        public function offsetUnset($offset) {
            $this->openRW();

            dba_delete($offset, $this->handle);
        }

        private function openR() {
            if(!$this->canRead) {
                if(file_exists($this->file)) {
                    $this->handle = dba_open($this->file, 'r'.self :: $lockFlag , self :: $handler);
                }
                $this->canRead = true;
            }
        }

        private function openRW() {
            if(!$this->canWrite) {
                if($this->handle) {
                    dba_close($this->handle);
                }

                $this->handle = @dba_open($this->file, 'c'.self::$lockFlag, self :: $handler);

                // db4 can yield an fopen_meta_data error,
                // where in the cases of locking with +d creates the file in
                // order to achieve the lock, but does treat the file as newly created
                // for a lock. Reopening the database with truncate and no lock solves
                // this issue.
                if($this->handle === false) {
                    unlink($this->file);

                    $this->handle = dba_open($this->file, 'n-', self :: $handler);
                }

                $this->canWrite = true;
                $this->canRead = true;
                	
            }
        }

        public function clear() {
            $this->openRW();
            $key = dba_firstkey($this->handle);
            while($key) {
                dba_delete($key, $this->handle);
                $key = dba_nextkey($this->handle);
            }
        }

        public function getOffsets() {
            $this->openR();
            $keys = array();

            if(!$this->handle) {
                return $keys;
            }

            $key = dba_firstkey($this->handle);

            while($key) {
                $keys[]=$key;
                $key = dba_nextkey($this->handle);
            }

            return $keys;
        }


        public function current() {
            $this->openR();
            if($this->handle) {
                return dba_fetch($this->key, $this->handle);
            } else {
                return null;
            }
        }

        public function key() {
            return $this->key;
        }

        public function next() {
            $this->openR();
            if($this->handle) {
                $this->key = dba_nextkey($this->handle);
            }
        }

        public function rewind() {
            $this->openR();
            if($this->handle) {
                $this->key = dba_firstkey($this->handle);
            }
        }

        public function valid() {
            return $this->key !== false;
        }

        public function count() {
            $this->openR();
            if(!$this->handle) {
                return 0;
            }

            $key = dba_firstkey($this->handle);
            $result = 0;
            while($key !== false) {
                $key = dba_nextkey($this->handle);
                $result++;
            }
            return $result;
        }

    }

    class DBAStorageProvider extends StorageProvider {

        private $root = null;
        private $os = null;

        public function __construct($root, $os) {
            $this->root = $root;
            $this->os = $os;
        }

        public function createStorage($storageName) {
            return new DBAStorage($storageName, $this->root, $this->os);
        }

    }